/*
 * Copyright (C) 2011 Markus Junginger, greenrobot (http://greenrobot.de)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mlizhi.base.dao.query;

import java.util.List;

import android.database.Cursor;
import android.os.Process;
import com.mlizhi.base.dao.AbstractDao;
import com.mlizhi.base.dao.DaoException;

/**
 * A repeatable query returning entities.
 * 
 * @author Markus
 * 
 * @param <T>
 *The enitity class the query will return results for.
 */
// TODO support long, double and other types, not just Strings, for parameters
// TODO Make parameters setable by Property (if unique in paramaters)
// TODO Query for PKs/ROW IDs
// TODO Make query compilable
public class Query<T> extends AbstractQuery<T> {
private final static class QueryData<T2> extends AbstractQueryData<T2, Query<T2>> {
private final int limitPosition;
private final int offsetPosition;

QueryData(AbstractDao<T2, ?> dao, String sql, String[] initialValues, int limitPosition, int offsetPosition) {
super(dao,sql,initialValues);
this.limitPosition = limitPosition;
this.offsetPosition = offsetPosition;
}

@Override
protected Query<T2> createQuery() {
return new Query<T2>(this, dao, sql, initialValues.clone(), limitPosition, offsetPosition);
}

}

/** For internal use by greenDAO only. */
public static <T2> Query<T2> internalCreate(AbstractDao<T2, ?> dao, String sql, Object[] initialValues) {
return create(dao, sql, initialValues, -1, -1);
}

static <T2> Query<T2> create(AbstractDao<T2, ?> dao, String sql, Object[] initialValues, int limitPosition,
int offsetPosition) {
QueryData<T2> queryData = new QueryData<T2>(dao, sql, toStringArray(initialValues), limitPosition,
offsetPosition);
return queryData.forCurrentThread();
}

private final int limitPosition;
private final int offsetPosition;
private final QueryData<T> queryData;

private Query(QueryData<T> queryData, AbstractDao<T, ?> dao, String sql, String[] initialValues, int limitPosition,
int offsetPosition) {
super(dao, sql, initialValues);
this.queryData = queryData;
this.limitPosition = limitPosition;
this.offsetPosition = offsetPosition;
}

public Query<T> forCurrentThread() {
return queryData.forCurrentThread(this);
}

/**
 * Sets the parameter (0 based) using the position in which it was added during building the query.
 */
public void setParameter(int index, Object parameter) {
if (index >= 0 && (index == limitPosition || index == offsetPosition)) {
throw new IllegalArgumentException("Illegal parameter index: " + index);
}
super.setParameter(index, parameter);
}

/**
 * Sets the limit of the maximum number of results returned by this Query. {@link QueryBuilder#limit(int)} must have
 * been called on the QueryBuilder that created this Query object.
 */
public void setLimit(int limit) {
checkThread();
if (limitPosition == -1) {
throw new IllegalStateException("Limit must be set with QueryBuilder before it can be used here");
}
parameters[limitPosition] = Integer.toString(limit);
}

/**
 * Sets the offset for results returned by this Query. {@link QueryBuilder#offset(int)} must have been called on the
 * QueryBuilder that created this Query object.
 */
public void setOffset(int offset) {
checkThread();
if (offsetPosition == -1) {
throw new IllegalStateException("Offset must be set with QueryBuilder before it can be used here");
}
parameters[offsetPosition] = Integer.toString(offset);
}

/** Executes the query and returns the result as a list containing all entities loaded into memory. */
public List<T> list() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return daoAccess.loadAllAndCloseCursor(cursor);
}

/**
 * Executes the query and returns the result as a list that lazy loads the entities on first access. Entities are
 * cached, so accessing the same entity more than once will not result in loading an entity from the underlying
 * cursor again.Make sure to close it to close the underlying cursor.
 */
public LazyList<T> listLazy() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return new LazyList<T>(daoAccess, cursor, true);
}

/**
 * Executes the query and returns the result as a list that lazy loads the entities on every access (uncached). Make
 * sure to close the list to close the underlying cursor.
 */
public LazyList<T> listLazyUncached() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return new LazyList<T>(daoAccess, cursor, false);
}

/**
 * Executes the query and returns the result as a list iterator; make sure to close it to close the underlying
 * cursor. The cursor is closed once the iterator is fully iterated through.
 */
public CloseableListIterator<T> listIterator() {
return listLazyUncached().listIteratorAutoClose();
}

/**
 * Executes the query and returns the unique result or null.
 * 
 * @throws DaoException
 * if the result is not unique
 * @return Entity or null if no matching entity was found
 */
public T unique() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return daoAccess.loadUniqueAndCloseCursor(cursor);
}

/**
 * Executes the query and returns the unique result (never null).
 * 
 * @throws DaoException
 * if the result is not unique or no entity was found
 * @return Entity
 */
public T uniqueOrThrow() {
T entity = unique();
if (entity == null) {
throw new DaoException("No entity found for query");
}
return entity;
}

}
